<?php
/**
 * Created by PhpStorm.
 * User: lelouchms
 * Date: 2019/3/28
 * Time: 16:09
 */

namespace app\backend\controller\admin\v1;


use app\backend\controller\BackendController;
use app\common\exception\AuthenticationException;
use app\common\exception\CustomerException;
use app\common\exception\LoginException;
use app\common\exception\ValidateException;
use app\common\library\JWTToken;
use app\common\util\CommonUtil;
use app\common\util\RandomUtil;
use think\Exception;
use think\facade\Cache;

abstract class Base extends BackendController
{


    /**
     * 自定义where条件(用于index查询)
     * @var array
     */
    protected $where = [];
    /**
     * 排除的查询字段
     * @var array
     */
    protected $expectFields = [];
    /**
     * @var Model
     */
    protected $model = null;

    /**
     * 快速搜索时执行查找的字段
     */
    protected $searchFields = "id";
    /**
     * 是否是关联查询
     */
    protected $relationSearch = false;
    /**
     * 指定主键值
     * @var string
     */
    protected $idField = "id";
    /**
     * 操作条件植入
     * @var array
     */
    protected $actionWhere = [];


    protected $tableName = "";

    protected function getTableName()
    {
        if (!empty($this->tableName)) return $this->tableName;
        if (!empty($this->model)) {
            $this->tableName = \think\Loader::parseName(basename(str_replace('\\', '/', get_class($this->model)))) . ".";
            return $this->tableName;
        }
        return '';
    }

    protected function dataTablePrefix($data)
    {
        $wrapperData = [];
        $tableName = $this->getTableName();
        foreach ($data as $key => $value) {
            $wrapperData[$tableName . $key] = $value;
        }
        return $wrapperData;
    }


    /**
     * 生成查询所需要的条件,排序方式
     * @param boolean $relationSearch 是否关联查询
     * @return array
     */
    protected function buildParams($relationSearch = false)
    {
        $relationSearch = is_null($relationSearch) ? $this->relationSearch : $relationSearch;
        $search = $this->request->get("search", '');
        $filter = $this->request->get("filter", '');
        $op = $this->request->get("options", '', 'trim');
        $sort = $this->request->get("sort", !empty($this->getTableName()) ? $this->getTableName() . $this->idField : $this->idField);
        $order = $this->request->get("order", "DESC");
        $offset = $this->request->get("offset", 0);
        $limit = $this->request->get("limit", 0);
        $infinite = $this->request->get("infinite", false);
        $filter = (array)json_decode($filter, true);
        $op = (array)json_decode($op, true);
        $filter = $filter ? $filter : [];
        $where = [];
        $tableName = '';
        if ($relationSearch) {
            if (!empty($this->model)) {
                $tableName = $this->getTableName();
            }
            $sortArr = explode(',', $sort);
            foreach ($sortArr as $index => & $item) {
                $item = stripos($item, ".") === false ? $tableName . trim($item) : $item;
            }
            unset($item);
            $sort = implode(',', $sortArr);
        }
        if (!empty($this->where)) {
            foreach ($this->where as $key => $value) {
                //  额外条件
                if (isset($value["option"])) {
                    if (!empty($value["value"])) $where[] = [$tableName . $key, $value["option"], $value["value"]];
                } else {
                    if (!empty($value)) $where[] = [$tableName . $key, "=", $value];
                }
            }
        }
        foreach ($filter as $k => $v) {
            $sym = isset($op[$k]) ? $op[$k] : '=';
            if (stripos($k, ".") === false) {
                $k = $tableName . $k;
            }
            $v = !is_array($v) ? trim($v) : $v;
            $sym = strtoupper(isset($op[$k]) ? $op[$k] : $sym);
            if(empty($v))continue;
            switch ($sym) {
                case '=':
                case '!=':
                    $where[] = [$k, $sym, (string)$v];
                    break;
                case 'LIKE':
                case 'NOT LIKE':
                case 'LIKE %...%':
                case 'NOT LIKE %...%':
                    $where[] = [$k, trim(str_replace('%...%', '', $sym)), "%{$v}%"];
                    break;
                case '>':
                case '>=':
                case '<':
                case '<=':
                    $where[] = [$k, $sym, intval($v)];
                    break;
                case 'FINDIN':
                case 'FINDINSET':
                case 'FIND_IN_SET':
                    $where[] = "FIND_IN_SET('{$v}', " . ($relationSearch ? $k : '`' . str_replace('.', '`.`', $k) . '`') . ")";
                    break;
                case 'IN':
                case 'IN(...)':
                case 'NOT IN':
                case 'NOT IN(...)':
                    $where[] = [$k, str_replace('(...)', '', $sym), is_array($v) ? $v : explode(',', $v)];
                    break;
                case 'BETWEEN':
                case 'NOT BETWEEN':
                    $arr = array_slice(explode(',', $v), 0, 2);
                    if (stripos($v, ',') === false || !array_filter($arr))
                        continue 2;
                    //当出现一边为空时改变操作符
                    if ($arr[0] === '') {
                        $sym = $sym == 'BETWEEN' ? '<=' : '>';
                        $arr = $arr[1];
                    } else if ($arr[1] === '') {
                        $sym = $sym == 'BETWEEN' ? '>=' : '<';
                        $arr = $arr[0];
                    }
                    $where[] = [$k, $sym, $arr];
                    break;
                case 'RANGE':
                case 'NOT RANGE':
                    $v = str_replace(' - ', ',', $v);
                    $arr = array_slice(explode(',', $v), 0, 2);
                    if (stripos($v, ',') === false || !array_filter($arr))
                        continue 2;
                    //当出现一边为空时改变操作符
                    if ($arr[0] === '') {
                        $sym = $sym == 'RANGE' ? '<=' : '>';
                        $arr = $arr[1];
                    } else if ($arr[1] === '') {
                        $sym = $sym == 'RANGE' ? '>=' : '<';
                        $arr = $arr[0];
                    }
                    $where[] = [$k, str_replace('RANGE', 'BETWEEN', $sym) . ' time', $arr];
                    break;
                case 'LIKE':
                case 'LIKE %...%':
                    $where[] = [$k, 'LIKE', "%{$v}%"];
                    break;
                case 'NULL':
                case 'IS NULL':
                case 'NOT NULL':
                case 'IS NOT NULL':
                    $where[] = [$k, strtolower(str_replace('IS ', '', $sym))];
                    break;
                default:
                    break;
            }
        }
        $where = function ($query) use ($where) {
            foreach ($where as $k => $v) {
                if (is_array($v)) {
                    call_user_func_array([$query, 'where'], $v);
                } else {
                    $query->where($v);
                }
            }
        };
        return [$where, $sort, $order, $offset, $limit, $infinite];
    }

    private function _actionAppendWhere($query)
    {
        foreach ($this->actionWhere as $key => $value) {
            //  额外条件
            if (isset($value["option"])) {
                $query->where($key, $value["option"], $value["value"]);
            } else {
                $query->where($key, $value);
            }
        }
    }

    protected function _removeCache($params)
    {
        if (!is_array($params[$this->idField])) $params[$this->idField] = explode(",", $params[$this->idField]);
        foreach ($params[$this->idField] as $key => $value) {
            Cache::rm($this->model->getName() . ":" . $value);
        }
    }

    public function index()
    {
        list($where, $sort, $order, $offset, $limit, $infinite) = $this->buildParams();
        $count = $this->model->where($where)->order($sort, $order)->count();
        $query = $this->model
            ->allowField(true)
            ->where($where)
            ->order($sort, $order);
        if ($infinite !== true) {
            $query->limit($offset, $limit);
        }
        if (!empty($this->expectFields)) $query->field($this->dataTablePrefix($this->expectFields), true);
        $list = $query->select();
        $returnData = [
            "count" => $count,
            "list" => $list
        ];
        if (method_exists($this->model, "getDictionary")) {
            $returnData["dictionary"] = $this->model->getDictionary();
        }
        return $returnData;
    }

    public function findById($id)
    {
        if (empty($id)) throw new ValidateException("缺少参数" . $this->idField);
        $query = $this->model->where($this->idField, $id)->field($this->expectFields, true);
        $this->_actionAppendWhere($query);
        $findData = $query->find();
        if (empty($findData)) throw new ValidateException("未找到指定的数据信息");
        return $findData;
    }

    public function reverseStatus()
    {
        $params = $this->request->param();
        if (empty($params[$this->idField])) throw new ValidateException("缺少参数" . $this->idField);
        if (empty($params['status'])) throw new ValidateException("缺少参数status");
        if (!in_array($params['status'], [1, 2])) throw new ValidateException("状态值错误");
        $query = $this->model->where($this->idField, "in", $params[$this->idField]);
        $this->_actionAppendWhere($query);
        $query->update(["status" => $params['status']]);
        $this->_removeCache($params);
    }

    public function delete()
    {
        $params = $this->request->param();
        if (empty($params[$this->idField])) throw new ValidateException("缺少参数" . $this->idField);
        $idCount = is_array($params[$this->idField]) ? count($params[$this->idField]) : count(explode(",", $params[$this->idField]));
        $query = $this->model->where($this->idField, "in", $params[$this->idField]);
        $this->_actionAppendWhere($query);
        $count = $query->delete();
        if ($count > $idCount) throw new CustomerException("操作失败");
        $this->_removeCache($params);
    }


    protected function loginByPassword($username, $password)
    {
        $adminUser = \app\common\model\Admin::where("username", $username)->find();
        if (empty($adminUser)) throw new LoginException(LoginException::USERNAME_NOT_EXISTS);
        if (intval($adminUser['status']) != \app\common\model\Admin::STATUS_ENABLED) throw new LoginException(LoginException::USER_DISABLED);
        $encodePassword = CommonUtil::encodePassword($password, $adminUser['salt']);
        if ($encodePassword !== $adminUser['password']) throw new LoginException(LoginException::PASSWORD_ERROR);
        $adminUser->logintime = date("Y-m-d H:i:s");
        $adminUser->token = (string)JWTToken::getToken($adminUser["id"], "api-backend-admin");
        $adminUser->save();
        $adminUser['avatar'] = CommonUtil::formatHostUrl($adminUser['avatar']);
        return $adminUser->toArray();
    }

    protected function loginByToken($token)
    {
        $tokenData = JWTToken::parse($token, "api-backend-admin");
        try {
            $adminUser = (new \app\common\model\Admin())->findById($tokenData['user_id']);
            if (intval($adminUser['status']) !== \app\common\model\Admin::STATUS_ENABLED) throw new LoginException(LoginException::USER_DISABLED);
            if ($adminUser["token"] !== $token) throw new LoginException(LoginException::LOGIN_EXPIRE);
            return $adminUser->toArray();
        } catch (CustomerException $e) {
            if ($e instanceof LoginException) throw $e;
            if ($e instanceof CustomerException) new LoginException(LoginException::USERNAME_NOT_EXISTS);
            throw new CustomerException("登录异常: " . $e->getMessage());
        }
    }

    public function initialize()
    {
        header("Access-Control-Allow-Origin: *");
        parent::initialize(); // TODO: Change the autogenerated stub
        $controllerName = explode(".", $this->request->controller());
        $controllerName = strtolower($controllerName[count($controllerName) - 1]);
        $actionName = $this->request->action(true);
        $adminUser = session("backend_admin_user");
        //  登陆验证
        if (empty($adminUser) || !is_array($adminUser)) {
            if (!in_array(strtolower($actionName), $this->allow_action) && !in_array(strtolower($controllerName), $this->allow_controller)) {
                $token = $this->request->header("access_token", $this->request->param("access_token"));
                if (empty($token)) throw new LoginException(LoginException::LOGIN_NONE);
                $adminUser = $this->loginByToken($token);
                $this->saveSession($adminUser);
            }
        }
        if (!empty($this->denyActions) && in_array($actionName, $this->denyActions)) {
            throw new AuthenticationException("无相关的访问权限");
        }
    }

    public function saveSession($adminUser)
    {
        session("backend_admin_user", $adminUser);
    }

}